﻿using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.MemoryProfiler;
using UnityEditorInternal;
using UnityEngine;

namespace PerfAssist.MemoryProfilerAdvanced.Editor.PAContrib
{
    public class MemUtil
    {
        public const int PLAYER_DIRECT_IP_CONNECT_GUID = 65261;

        private static readonly Regex _ipReg =
            new Regex(
                @"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");

        public static string SnapshotsDir = string.Format("{0}/mem_snapshots", Application.persistentDataPath);
        public static bool IsProfilerConnectedRemotely
        {
            get { return ProfilerDriver.connectedProfiler == PLAYER_DIRECT_IP_CONNECT_GUID; }
        }

        public static bool ValidateIPString(string ip)
        {
            return !string.IsNullOrEmpty(ip) && _ipReg.IsMatch(ip);
        }

        public static bool IsLocalhostIP(string ip)
        {
            return ip == MemConst.LocalhostIP;
        }

        public static void NotifyError(string format, params object[] args)
        {
            var content = string.Format(format, args);
            Debug.LogError(content);
            EditorWindow.focusedWindow.ShowNotification(new GUIContent(content));
        }

        public static string GetFullpath(string filename)
        {
            return string.IsNullOrEmpty(filename) ? "" : string.Format("{0}/{1}", SnapshotsDir, filename);
        }

        public static string[] GetFiles()
        {
            try
            {
                if (!Directory.Exists(SnapshotsDir)) Directory.CreateDirectory(SnapshotsDir);

                var files = Directory.GetFiles(SnapshotsDir);
                for (var i = 0; i < files.Length; i++)
                {
                    var begin = files[i].LastIndexOfAny(new[] {'\\', '/'});
                    if (begin != -1) files[i] = files[i].Substring(begin + 1);
                }

                return files;
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
                return new string[] { };
            }
        }

        public static PackedMemorySnapshot Load(string filename, bool needsFullpath = false)
        {
            try
            {
                if (string.IsNullOrEmpty(filename))
                    throw new Exception("bad_load: filename is empty.");

                var filepath = needsFullpath ? GetFullpath(filename) : filename;
                if (!File.Exists(filepath))
                    throw new Exception(string.Format("bad_load: file not found. ({0})", filepath));

                using (Stream stream = File.Open(filepath, FileMode.Open))
                {
                    return new BinaryFormatter().Deserialize(stream) as PackedMemorySnapshot;
                }
            }
            catch (Exception ex)
            {
                Debug.LogErrorFormat("bad_load: exception occurs while loading '{0}'.", filename);
                Debug.LogException(ex);
                return null;
            }
        }

        public static string Save(PackedMemorySnapshot snapshot)
        {
            try
            {
                var filename = GetFullpath(string.Format("{0}-{1}.memsnap",
                    SysUtil.FormatDateAsFileNameString(DateTime.Now),
                    SysUtil.FormatTimeAsFileNameString(DateTime.Now)));

                var bf = new BinaryFormatter();
                using (Stream stream = File.Open(filename, FileMode.Create))
                {
                    bf.Serialize(stream, snapshot);
                }

                return filename;
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
                return "";
            }
        }

        public static string GetGroupName(ThingInMemory thing)
        {
            if (thing is NativeUnityEngineObject)
                return (thing as NativeUnityEngineObject).className ?? "MissingName";
            if (thing is ManagedObject)
                return (thing as ManagedObject).typeDescription.name;
            return thing.GetType().Name;
        }

        public static int GetCategory(ThingInMemory thing)
        {
            if (thing is NativeUnityEngineObject)
                return 1;
            if (thing is ManagedObject)
                return 2;
            if (thing is GCHandle)
                return 3;
            if (thing is StaticFields)
                return 4;

            return 5;
        }

        public static string GetCategoryLiteral(ThingInMemory thing)
        {
            if (thing is NativeUnityEngineObject)
                return "[native] ";
            if (thing is ManagedObject)
                return "[managed] ";
            if (thing is GCHandle)
                return "[gchandle] ";
            if (thing is StaticFields)
                return "[static] ";

            return "[unknown] ";
        }

        public static bool MatchSizeLimit(int size, int curLimitIndex)
        {
            if (curLimitIndex == 0)
                return true;

            switch (curLimitIndex)
            {
                case 0:
                    return true;

                case 1:
                    return size >= MemConst._1MB;

                case 2:
                    return size >= MemConst._1KB && size < MemConst._1MB;

                case 3:
                    return size < MemConst._1KB;

                default:
                    return false;
            }
        }

        public static void LoadSnapshotProgress(float progress, string tag)
        {
            EditorUtility.DisplayProgressBar("Loading in progress, please wait...",
                string.Format("{0} - {1}%", tag, (int) (progress * 100.0f)), progress);

            if (progress >= 1.0f)
                EditorUtility.ClearProgressBar();
        }

        public static string IntStrWithSign(int val)
        {
            return val.ToString("+#;-#;0");
        }

        public static string GetSign(int val)
        {
            return val >= 0 ? "+" : "-";
        }

        public static string GetThingContent(ThingInMemory thing, CrawledMemorySnapshot unpacked)
        {
            var mo = thing as ManagedObject;

            if (mo != null)
                switch (mo.typeDescription.name)
                {
                    case "System.String":
                        try
                        {
                            return StringTools.ReadString(
                                unpacked.managedHeap.Find(mo.address, unpacked.virtualMachineInformation),
                                unpacked.virtualMachineInformation);
                        }
                        catch (Exception ex)
                        {
                            //UnityEngine.Debug.LogErrorFormat("StringTools.ReadString happens error .things caption = {0} ,ex ={1} ", thing.caption, ex.ToString());
                            var bo = unpacked.managedHeap.Find(mo.address, unpacked.virtualMachineInformation);
                            if (bo.bytes == null)
                            {
                                Debug.LogErrorFormat(
                                    "error string,find address bytes is null ,caption = {0},address = {1},exception ={2}",
                                    thing.caption, mo.address, ex);
                                return string.Format(
                                    "error string,find address bytes is null ,caption = {0},address = {1},exception ={2}",
                                    thing.caption, mo.address, ex);
                            }

                            var lengthPointer = bo.Add(unpacked.virtualMachineInformation.objectHeaderSize);
                            var length = lengthPointer.ReadInt32();
                            var firstChar = lengthPointer.Add(4);
                            Debug.LogErrorFormat(
                                "error string,expect caption = {0} ,length = {1},firstChar ={2},address = {3},exception ={4}",
                                thing.caption, length, firstChar, mo.address, ex);
                            return string.Format(
                                "error string,expect caption = {0} ,length = {1},firstChar ={2},address = {3},exception ={4}",
                                thing.caption, length, firstChar, mo.address, ex);
                        }
                }

            return thing.caption;
        }
    }
}